Grid = class()

function Grid:init()
    for y = 1, HEIGHT do
        self[y] = {}
    end
end

function Grid:set(x, y, c, char, room)
    if not room then
        room = self[y][x].room
    end
    self[y][x] = c(x, y, char, room)
end

function Grid:update(world, key)
    for y = 1, HEIGHT do
        for x = 1, WIDTH do
            self[y][x]:update(world)
        end
    end
end

function Grid:draw(text)
    for y = 1, HEIGHT do
        for x = 1, WIDTH do
            if self[y][x].lit then
                text[y][x] = self[y][x]
            end
        end
    end
end

-- draws
function Grid:room(r)
    local room = {}

    a, b = r[1], r[2]

    for y = a.y + 1, b.y - 1 do
        for x = a.x + 1, b.x - 1 do
            self:set(x, y, Air, '.', room)
            room[#room + 1] = {x = x, y = y}
        end
    end

    for y = a.y, b.y do
        for x = a.x, b.x do
            if not self[y][x] then
                self:set(x, y, Wall, '#', {})
            end
        end
    end
end

function rectIntersects(a, b)
    return a[1].x <= b[2].x and b[1].x <= a[2].x and a[1].y <= b[2].y and b[1].y <= a[2].y
end

function roomAround(v)
    return {
        {
            x = math.max(1, v.x - love.math.random(3, 6)),
            y = math.max(1, v.y - love.math.random(3, 6))
        },
        {
            x = math.min(WIDTH - 1, v.x + love.math.random(3, 6)),
            y = math.min(HEIGHT - 1, v.y + love.math.random(3, 6))
        }
    }
end

function randomRoom()
    return roomAround(randomPoint())
end

function Grid:emptySpace()
    for y = 1, HEIGHT do
        for x = 1, WIDTH do
            if not self[y][x] then
                self[y][x] = Wall(x, y, ' ', {})
            end
        end
    end
end

function Grid:lightAll()
    for y = 1, HEIGHT do
        for x = 1, WIDTH do
            self[y][x].lit = true
        end
    end
end

-- straight line (has to be along a direct cardinal or whatever you call it)
function Grid:line(a, b)
    local x, y = a.x, a.y
    local vx, vy = 0, 0
    local out = {a}

    if b.x > a.x then
        vx = 1
    elseif b.x < a.x then
        vx = -1
    end
    if b.y > a.y then
        vy = 1
    elseif b.y < a.y then
        vy = -1
    end

    while x ~= b.x or y ~= b.y do
        x = x + vx
        y = y + vy
        out[#out + 1] = {x = x, y = y}
    end

    return out
end

function Grid:lineAir(a, b)
    l = self:line(a, b)

    for _, v in pairs(l) do
        self:set(v.x, v.y, Air, '.', {})
    end
end

-- random, maze-y path to an area
function Grid:path(a, b)
    local v = cloneVec(a) -- Lua pls
    local vx, vy = 0, 0
    local out = {}

    if b.x > a.x then
        vx = 1
    elseif b.x < a.x then
        vx = -1
    end
    if b.y > a.y then
        vy = 1
    elseif b.y < a.y then
        vy = -1
    end

    while v.x ~= b.x or v.y ~= b.y do
        local line

        if love.math.random(1, 2) == 1 and b.x ~= v.x then
            local amount = math.min(love.math.random(5), math.abs(b.x - v.x))
            line = self:line(v, {x = v.x + math.max(0, amount - 1) * vx, y = v.y})
            v.x = v.x + amount * vx
        else
            local amount = math.min(love.math.random(5), math.abs(b.y - v.y))
            line = self:line(v, {y = v.y + math.max(0, amount - 1) * vy, x = v.x})
            v.y = v.y + amount * vy
        end

        for _, v in pairs(line) do
            out[#out + 1] = cloneVec(v)
        end
    end

    out[#out + 1] = cloneVec(v)

    return out
end

function Grid:pathAir(a, b)
    l = self:path(a, b)

    for _, v in pairs(l) do
        self:set(v.x, v.y, Air, '.', {})
    end
end

function randomInRoom(r)
    return {
        x = love.math.random(r[1].x + 1, r[2].x - 1),
        y = love.math.random(r[1].y + 1, r[2].y - 1)
    }
end

function randomPoint()
    return {
        x = love.math.random(1, WIDTH - 1),
        y = love.math.random(1, HEIGHT - 1)
    }
end

function Grid:generateLevel(start)
    local rooms = {roomAround(start)}

    for i = 1, love.math.random(5, 10) do
        local newRoom
        local collide = true

        while collide do
            newRoom = randomRoom()
            collide = false
            for _, r in pairs(rooms) do
                if rectIntersects(newRoom, r) then
                    collide = true
                end
            end
        end

        local oldRoom = rooms[love.math.random(#rooms)]
        self:pathAir(randomInRoom(oldRoom), randomInRoom(newRoom))
        rooms[#rooms + 1] = newRoom
    end

    for _, r in pairs(rooms) do
        self:room(r)
    end

    for i = 1, self.level == 1 and 6 or love.math.random(1, 3) do
        p = randomInRoom(rooms[love.math.random(2, #rooms)])
        self:set(p.x, p.y, Stair, '>')
    end

    for i = 1, self.level == 1 and 6 or love.math.random(1, 6) do
        p = randomInRoom(rooms[love.math.random(1, #rooms)])
        self:set(p.x, p.y, Chest, 'C')
    end

    self:emptySpace()
end
